[Chapter Eleven][Previous]
[Next] [Art of
Assembly][Randall Hyde]
Art of Assembly: Chaper Eleven
- 11.5.7 - Passing Parameters in Registers
- 11.5.8 - Passing Parameters in Global
Variables
11.5.7 Passing Parameters in Registers
Having touched on how to pass parameters to a procedure, the next thing
to discuss is where to pass parameters. Where you pass parameters depends,
to a great extent, on the size and number of those parameters. If you are
passing a small number of bytes to a procedure, then the registers are an
excellent place to pass parameters. The registers are an ideal place to
pass value parameters to a procedure. If you are passing a single parameter
to a procedure you should use the following registers for the accompanying
data types:
Data Size Pass in this Register
Byte: al
Word: ax
Double Word: dx:ax or eax (if 80386 or better)
This is, by no means, a hard and fast rule. If you find it more convenient
to pass 16 bit values in the si
or bx
register,
by all means do so. However, most programmers use the registers above to
pass parameters.
If you are passing several parameters to a procedure in the 80x86's registers,
you should probably use up the registers in the following order:
First Last
ax, dx, si, di, bx, cx
In general, you should avoid using bp
register. If you need
more than six words, perhaps you should pass your values elsewhere.
The UCR Standard Library package provides several good examples of procedures
that pass parameters by value in the registers. Putc
, which
outputs an ASCII character code to the video display, expects an ASCII value
in the al
register. Likewise, puti
expects the
value of a signed integer in the ax
register. As another example,
consider the following putsi
(put short integer) routine that
outputs the value in al
as a signed integer:
putsi proc
push ax ;Save AH's value.
cbw ;Sign extend AL -> AX.
puti ;Let puti do the real work.
pop ax ;Restore AH.
ret
putsi endp
The other four parameter passing mechanisms (pass by reference, value-returned,
result, and name) generally require that you pass a pointer to the desired
object (or to a thunk in the case of pass by name). When passing such parameters
in registers, you have to consider whether you're passing an offset or a
full segmented address. Sixteen bit offsets can be passed in any of the
80x86's general purpose 16 bit registers. si
, di
,
and bx
are the best place to pass an offset since you'll probably
need to load it into one of these registers anyway[4].
You can pass 32 bit segmented addresses dx:ax
like other double
word parameters. However, you can also pass them in ds:bx
,
ds:si
, ds:di
, es:bx
, es:si
,
or es:di
and be able to use them without copying into a segment
register.
The UCR Stdlib routine puts
, which prints a string to the video
display, is a good example of a subroutine that uses pass by reference.
It wants the address of a string in the es:di
register pair.
It passes the parameter in this fashion, not because it modifies the parameter,
but because strings are rather long and passing them some other way would
be inefficient. As another example, consider the following strfill(str,c)
that copies the character c
(passed by value in al
)
to each character position in str
(passed by reference in es:di
)
up to a zero terminating byte:
; strfill- copies value in al to the string pointed at by es:di
; up to a zero terminating byte.
byp textequ <byte ptr>
strfill proc
pushf ;Save direction flag.
cld ;To increment D with STOS.
push di ;Save, because it's changed.
jmp sfStart
sfLoop: stosb ;es:[di] := al, di := di + 1;
sfStart: cmp byp es:[di], 0 ;Done yet?
jne sfLoop
pop di ;Restore di.
popf ;Restore direction flag.
ret
strfill endp
When passing parameters by value-returned or by result to a subroutine,
you could pass in the address in a register. Inside the procedure you would
copy the value pointed at by this register to a local variable (value-returned
only). Just before the procedure returns to the caller, it could store the
final result back to the address in the register.
The following code requires two parameters. The first is a pass by value-returned
parameter and the subroutine expects the address of the actual parameter
in bx
. The second is a pass by result parameter whose address
is in si
. This routine increments the pass by value-result
parameter and stores the previous result in the pass by result parameter:
; CopyAndInc- BX contains the address of a variable. This routine
; copies that variable to the location specified in SI
; and then increments the variable BX points at.
; Note: AX and CX hold the local copies of these
; parameters during execution.
CopyAndInc proc
push ax ;Preserve AX across call.
push cx ;Preserve CX across call.
mov ax, [bx] ;Get local copy of 1st parameter.
mov cx, ax ;Store into 2nd parm's local var.
inc ax ;Increment 1st parameter.
mov [si], cx ;Store away pass by result parm.
mov [bx], ax ;Store away pass by value/ret parm.
pop cx ;Restore CX's value.
pop ax ;Restore AX's value.
ret
CopyAndInc endp
To make the call CopyAndInc(I,J) you would use code like the following:
lea bx, I
lea si, J
call CopyAndInc
This is, of course, a trivial example whose implementation is very inefficient.
Nevertheless, it shows how to pass value-returned and result parameters
in the 80x86's registers. If you are willing to trade a little space for
some speed, there is another way to achieve the same results as pass by
value-returned or pass by result when passing parameters in registers. Consider
the following implementation of CopyAndInc
:
CopyAndInc proc
mov cx, ax ;Make a copy of the 1st parameter,
inc ax ; then increment it by one.
ret
CopyAndInc endp
To make the CopyAndInc(I,J) call, as before, you would use the following
80x86 code:
mov ax, I
call CopyAndInc
mov I, ax
mov J, cx
Note that this code does not pass any addresses at all; yet it has the same
semantics (that is, performs the same operations) as the previous version.
Both versions increment I
and store the pre-incremented version
into J
. Clearly the latter version is faster, although your
program will be slightly larger if there are many calls to CopyAndInc
in your program (six or more).
You can pass a parameter by name or by lazy evaluation in a register by
simply loading that register with the address of the thunk to call. Consider
the Panacea PassByName
procedure (see "Pass
by Name" on page 576). One implementation of this procedure could
be the following:
;PassByName- Expects a pass by reference parameter index
; passed in si and a pass by name parameter, item,
; passed in dx (the thunk returns the address in bx).
PassByName proc
push ax ;Preserve AX across call
mov word ptr [si], 0 ;Index := 0;
ForLoop: cmp word ptr [si], 10 ;For loop ends at ten.
jg ForDone
call dx ;Call thunk item.
mov word ptr [bx], 0 ;Store zero into item.
inc word ptr [si] ;Index := Index + 1;
jmp ForLoop
ForDone: pop ax ;Restore AX.
ret ;All Done!
PassByName endp
You might call this routine with code that looks like the following:
lea si, I
lea dx, Thunk_A
call PassByName
.
.
.
Thunk_A proc
mov bx, I
shl bx, 1
lea bx, A[bx]
ret
Thunk_A endp
The advantage to this scheme, over the one presented in the earlier section,
is that you can call different thunks, not just the ItemThunk
routine appearing in the earlier example.
11.5.8 Passing Parameters in Global Variables
Once you run out of registers, the only other (reasonable) alternative
you have is main memory. One of the easiest places to pass parameters is
in global variables in the data segment. The following code provides an
example:
mov ax, xxxx ;Pass this parameter by value
mov Value1Proc1, ax
mov ax, offset yyyy ;Pass this parameter by ref
mov word ptr Ref1Proc1, ax
mov ax, seg yyyy
mov word ptr Ref1Proc1+2, ax
call ThisProc
.
.
.
ThisProc proc near
push es
push ax
push bx
les bx, Ref1Proc1 ;Get address of ref parm.
mov ax, Value1Proc1 ;Get value parameter
mov es:[bx], ax ;Store into loc pointed at by
pop bx ; the ref parameter.
pop ax
pop es
ret
ThisProc endp
Passing parameters in global locations is inelegant and inefficient. Furthermore,
if you use global variables in this fashion to pass parameters, the subroutines
you write cannot use recursion (see "Recursion"
on page 606). Fortunately, there are better parameter passing schemes
for passing data in memory so you do not need to seriously consider this
scheme.
[4] This does not apply to thunks. You may
pass the address of a thunk in any 16 bit register. Of course, on an 80386
or later processor, you can use any of the 80386's 32-bit registers to hold
an address.
- 11.5.7 - Passing Parameters in Registers
- 11.5.8 - Passing Parameters in Global
Variables
Art of Assembly: Chaper Eleven - 27 SEP 1996
[Chapter Eleven][Previous]
[Next] [Art of
Assembly][Randall Hyde]